<?php

if (! defined('LIB_PATH'))
{
    die('The file ' . __FILE__ . ' needs to be required from the main executable of PhpCodeIndexer');
}

/**
 * @see Parser
 */
require_once LIB_PATH . 'Parser.php';

/**
 * This class is inheriting the PhpElement class
 *
 * @see PhpElement
 */
require_once LIB_PATH . 'PhpElement.php';

/**
 * Exception to be thrown when trying to load an uncatalogued element
 *
 * @see UncatalogedElementException
 */
require_once LIB_PATH . 'exceptions/UncatalogedElementException.php';

/**
 * PhpClass
 *
 * Class that represents a php class retrieved from source code
 * 
 * @uses PhpElement
 * @package php-phoenix
 * @subpackage models
 * @version $id$
 * @author Pablo López Torres <pablolopeztorres@gmail.com>
 */
class PhpClass extends PhpElement
{
    const ORDER_UNORDERED = 0x00;

    const ORDER_ALPHABETICALLY = 0x01;
    const ORDER_ACCESS_LEVEL = 0x02;

    const ORDER_BOTH = 0x03;

	/**
	 * className 
	 * 
	 * @var string
	 * @access private
	 */
	private $className;
	/**
	 * extends 
     *
	 * @var mixed
	 * @access private
	 */
	private $extends = NULL;

	/**
	 * implements 
	 * 
	 * @var array
	 * @access private
	 */
	private $implements = array();

	/**
	 * final 
	 * 
	 * @var mixed
	 * @access private
	 */
	private $final = FALSE;

	/**
	 * abstract 
	 * 
	 * @var mixed
	 * @access private
	 */
	private $abstract = FALSE;

	//
    // accessors
    //
	public function setAbstract($value)
	{
		$this->abstract = $value;
	}
	/**
	 * isAbstract
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function isAbstract()
	{
		return $this->abstract;
	}

	public function setFinal($value)
	{
		$this->final = $value;
	}

	/**
	 * isFinal
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function isFinal()
	{
		return $this->final;
	}

	public function setParentName($parent)
	{
		$this->extends = $parent;
	}
	/**
	 * getParentName
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function getParentName()
	{
		return $this->extends;
	}

	/**
	 * getParent
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function getParent()
	{
        $parent = NULL;
        if (isset($this->extends))
        {
            // @todo pablo - where this should be moved?
            // get the storage key that have the object
            $persistentKey = $this->getPersistentKey($this->extends);

            $parent = Storage::load($persistentKey);

            if ($parent === NULL)
            {
                throw new UncatalogedElementException($this->extends, $persistentKey);
            }
        }
        return $parent;
	}

	/**
	 * setInterfaceNames
	 * 
	 * @access public
	 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
	 * @param array $interfaceNames 
	 * @return void
	 */
	public function setInterfaceNames(array $interfaceNames)
	{
		$this->interfaces = $interfaceNames;
	}

	/**
	 * getImplementingNames
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function getInterfaceNames()
	{
		return $this->interfaces;
	}

	/**
	 * getImplementing
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function getInterfaces()
	{
		throw new Exception('not implemented yet');
	}


	/**
	 * fillFromHeader
	 * 
	 * @param mixed $tokens 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function fillFromHeader(&$tokens)
	{
		reset($tokens);

		$isAbstract = FALSE;
		$isFinal = FALSE;
		$className = NULL;
		$classExtends = NULL;
		$classImplements = array();
		$classContents = array();

		do
		{
			$token =& current($tokens);

			switch ($token[0])
			{
				case T_ABSTRACT:
					$isAbstract = TRUE;
					break;
				case T_FINAL:
					$isFinal = TRUE;
					break;
				case T_CLASS:
					$token =& next($tokens);
					$className = $token[1];
					break;
				case T_EXTENDS:
					$token =& next($tokens);
					$classExtends = $token[1];
					break;
				case T_IMPLEMENTS:
					while (next($tokens))
					{
						$token =& current($tokens);
						if ($token[0] === T_STRING)
						{
							$classImplements[] = $token[1];
						}
					}
					break;
				default:
                    print_r($token);
					throw new Exception('unexpected token!!');
					break;
			}

		}
		while (next($tokens));

		$success = FALSE;
		if (! empty($className))
		{
			$this->name = $className;
			$this->extends = $classExtends;
			$this->implements = $classImplements;

			$this->final = $isFinal;
			$this->abstract = $isAbstract;

			$success = TRUE;
		}

		return $success;
	}
	/**
	 * fillFromSource
	 *
	 * The pointer in the array should point to the T_USER_BRACE_OPEN that
	 * is the starting of the class
	 *
	 * @param mixed $tokens 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function fillFromSource(&$tokens)
	{
		print_r($tokens);
		die();
		// start parsing
		$token = current($tokens);

		$success = TRUE;

		// parse body
		$level = 0;
		if ($token[0] === T_USER_BRACE_OPEN)
		{
			do
			{
				switch ($token[0])
				{
					case T_CONST:
						$element = Parser::fetchConstant($tokens);

						if (isset($element))
						{
							$this->addElement($element);
						}

						break;

					case T_PRIVATE:
					case T_PUBLIC:
					case T_PROTECTED:
					case T_VAR:
					case T_FUNCTION:

						$element = Parser::fetchMethod($tokens);

						if (! isset($element))
						{
							$element = Parser::fetchVariable($tokens);
						}

						if (isset($element))
						{
							$this->addElement($element);
						}

						break;

					case T_USER_BRACE_OPEN:
						$level++;
						break;
					case T_USER_BRACE_CLOSE:
						$level--;
						break;
				}

				// clear it
				$element = NULL;

				next($tokens);
				$token =& current($tokens);

                if (! $token)
                {
                    // @todo pablo - comment out the trigger
                    // trigger_error('FATAL ERROR: Token is empty', E_USER_WARNING);
                    $exitLoop = TRUE; // @todo pablo - investigate this hack
                }
			}
			while ($level > 0 && ! isset($exitLoop));

		}

		return $success;
	}


	/**
	 * getElements
	 * 
	 * @author Pablo López Torres <pablolopeztorres@gmail.com>
	 * @access public
	 * @return void
	 */
	public function getElements($sorted = NULL, $parents = NULL)
	{
        if ($sorted === NULL)
        {
            $sorted = FALSE;
        }

        if ($parents === NULL)
        {
            $parents = FALSE;
        }

        $elements = $this->getElements();

        if ($parents)
        {
            try
            {
                $class = $this->getParent();
            }
            catch (UncatalogedElementException $e)
            {
                // do nothing... :(
                $class = NULL;
            }

            while ($class !== NULL)
            {
                $elements = array_merge($elements, $class->getElements());

                $class = $class->getParent();
            }
        }

        if ($sorted & self::ORDER_ALPHABETICALLY)
        {
            ksort($elements);
        }

        if ($sorted & self::ORDER_ACCESS_LEVEL)
        {
            $publicElements = array();
            $protectedElements = array();
            $privateElements = array();

            foreach ($elements as $name => $element)
            {
                switch ($element['access'])
                {
                    case 'public':
                        $publicElements[$name] = $element;
                        break;
                    case 'protected':
                        $protectedElements[$name] = $element;
                        break;
                    case 'private':
                        $privateElements[$name] = $element;
                        break;
                }
            }

            $elements = ($publicElements + $protectedElements + $privateElements);
        }

		return $elements;
	}


    // storage related
    /**
     * getPersistentKey
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param mixed $className 
     * @return void
     */
    public function getPersistentKey($className = NULL)
    {
        if ($className === NULL)
        {
            return $this->getProject()->getName() . '/class_' . $this->name;
        }
        else
        {
            return $this->getProject()->getName() . '/class_' . $className;
        }
    }

}

